package sg.edu.ntu.bloch.tictactoe.engine;

import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

import org.apache.log4j.Logger;

/**
 * @author hafiz
 *
 */
public class Board {
	
	private final static int MAX_PLAYERS = 2;
	private final static int WIN_CONDITION = 3;

	private static final Logger _log = Logger.getLogger(Board.class);
	private final int size;
	private final List<Player> playerList;

	private AtomicInteger moves;
	private State state = State.INITIAL;

	private Seed[][] slots;

	public Board(List<Player> playerList, int size)
			throws IllegalArgumentException {

		if (playerList.size() > MAX_PLAYERS) {
			throw new IllegalArgumentException(
					"Exceeded maximum number of players");
		}

		this.playerList = playerList;

		this.size = size;

		this.slots = new Seed[this.size][this.size];

		this.moves = new AtomicInteger();

		this.initSlots();
	}

	public State put(Seed seed) throws IllegalStateException {

		int x = seed.getX();
		int y = seed.getY();

		State state = State.CONTINUE;

		_log.debug("Put " + seed + "at: " + x + ":" + y);

		if (this.isSlotEmpty(seed.getX(), seed.getY())) {

			this.slots[x][y] = seed;
			this.moves.incrementAndGet();

		} else {
			throw new IllegalStateException(
					"Cannot put seed on an occupied slot");
		}

		state = this.checkWinningCondition(seed);
		
		Util.printBoard(this);
		_log.debug("State: "+ state);

		return state;
	}

	public Collection<Integer> load() {
		return null;
	}

	public boolean isSlotEmpty(int x, int y) {
		return this.slots[x][y] == null;
	}

	@Deprecated 
	public boolean canPutOrNot(Seed seed) {
		int x = seed.getX();
		int y = seed.getY();

		return this.slots[x][y] == null;
	}

	public List<Player> getPlayerList() {
		return playerList;
	}

	// TODO: refractor out to a util classs
	public int getSize() {
		return size;
	}


	public int getMoves() {
		return this.moves.intValue();
	}
	

	public Player getCurrentPlayer() {
		
		int playerIndex = this.moves.intValue() % this.playerList.size();
		
		return this.playerList.get(playerIndex);
	}
	
	public State getState() {
		return this.state;
	}

	
	public Seed getSeed(int x, int y) {
		
		if (this.isSlotEmpty(x, y)) {
			return null;
		} else {
			return this.slots[x][y];
		}
		
	}
	
	private void initSlots() {
		// Arrays.fill(this.slots, null);

		for (int i = 0; i < this.size; i++) {
			for (int j = 0; j < this.size; j++) {
				this.slots[i][j] = null;
			}
		}
	}

	private State checkWinningCondition(Seed seed) {

		if (this.state.equals(State.INITIAL) || this.state.equals(State.CONTINUE)) {
			this.state = this.checkHorizontal(seed);
		}

		if (this.state.equals(State.CONTINUE)) {
			this.state = this.checkVertical(seed);
		}

		if (this.state.equals(State.CONTINUE)) {
			this.state = this.checkBackDiagonal(seed);
		}

		if (this.state.equals(State.CONTINUE)) {
			this.state = this.checkForwardDiagonal(seed);
		}

		return this.state;
	}

	private State checkHorizontal(Seed seed) {

		int x = seed.getX();
		int y = seed.getY();
		
		int res = 0;

		res = this.checkEast(seed, x, y - 1, res);
		
		// winning condition
		if (res == Board.WIN_CONDITION - 1) {
			_log.debug("Statisfied winning condition on the east side");
			return State.WIN;
		}
		
		res = this.checkWest(seed, x, y + 1, res);
		
		// winning condition
		if (res == Board.WIN_CONDITION - 1) {
			_log.debug("Statisfied winning condition on the west side");
			return State.WIN;
		}

		return State.CONTINUE;
	}

	private int checkEast(Seed seed, int x, int y, int res) {
		_log.debug("CheckEast - x:" + x + " y:" + y + " res:" + res);

		Player currSlotOwner;

		// reached edge
		if (y == -1) {
			_log.debug("Reached east boundary");
			return res;
		}

		// empty slot
		if (this.slots[x][y] == null) {
			_log.debug("Empty slot.");
			return res;
		}

		// check slot owner
		currSlotOwner = this.slots[x][y].getPlayer();
		
		if (!seed.getPlayer().equals(currSlotOwner)) {
			_log.debug("Seed owner is different from current slot owner");
			return res;
		}
		
		if (seed.getPlayer().equals(currSlotOwner) && seed.getY() != y) {

			_log.debug("Seed owner equals to current slot owner");
			res++;
		}
		
		

		// winning condition (early pruning)
		if (res == Board.WIN_CONDITION - 1) {
			return res;
		}

		return this.checkEast(seed, x, y - 1, res);
	}

	private int checkWest(Seed seed, int x, int y, int res) {
		_log.debug("CheckWest - x:" + x + " y:" + y + " res:" + res);

		Player currSlotOwner;

		// reached edge
		if (y == this.size) {
			_log.debug("Reached west boundary");
			return res;
		}

		// empty slot
		if (this.slots[x][y] == null) {
			_log.debug("Empty slot.");
			return res;
		}

		// check slot owner
		currSlotOwner = this.slots[x][y].getPlayer();

		if (!seed.getPlayer().equals(currSlotOwner)) {
			_log.debug("Seed owner is different from current slot owner");
			return res;
		}
		
		if (seed.getPlayer().equals(currSlotOwner) && seed.getY() != y) {
			_log.debug("Seed owner equals to current slot owner");
			res++;
		}

		// winning condition (early pruning)
		if (res == Board.WIN_CONDITION - 1) {
			return res;
		}

		return this.checkWest(seed, x, y + 1, res);
	}

	private State checkVertical(Seed seed) {

		int x = seed.getX();
		int y = seed.getY();
		
		int res = 0;

		res = this.checkNorth(seed, x - 1, y, res);
		
		// winning condition
		if (res == Board.WIN_CONDITION - 1) {
			_log.debug("Statisfied winning condition on the north side");
			return State.WIN;
		}
		
		res = this.checkSouth(seed, x + 1, y, res);
		
		// winning condition
		if (res == Board.WIN_CONDITION - 1) {
			_log.debug("Statisfied winning condition on the south side");
			return State.WIN;
		}

		return State.CONTINUE;
	}

	private int checkNorth(Seed seed, int x, int y, int res) {
		_log.debug("checkNorth - x:" + x + " y:" + y + " res:" + res);

		Player currSlotOwner;

		// reached edge
		if (x == -1) {
			_log.debug("Reached north boundary");
			return res;
		}

		// empty slot
		if (this.slots[x][y] == null) {
			_log.debug("Empty slot.");
			return res;
		}

		// check slot owner
		currSlotOwner = this.slots[x][y].getPlayer();

		if (!seed.getPlayer().equals(currSlotOwner)) {
			_log.debug("Seed owner is different from current slot owner");
			return res;
		}
		
		if (seed.getPlayer().equals(currSlotOwner) && seed.getX() != x) {
			_log.debug("Seed owner equals to current slot owner");
			res++;
		}

		// winning condition (early pruning)
		if (res == Board.WIN_CONDITION - 1) {
			return res;
		}

		return this.checkNorth(seed, x - 1, y, res);
	}

	private int checkSouth(Seed seed, int x, int y, int res) {
		_log.debug("checkSouth - x:" + x + " y:" + y + " res:" + res);

		Player currSlotOwner;

		// reached edge
		if (x == this.size) {
			_log.debug("Reached south boundary");
			return res;
		}

		// empty slot
		if (this.slots[x][y] == null) {
			_log.debug("Empty slot.");
			return res;
		}

		// check slot owner
		currSlotOwner = this.slots[x][y].getPlayer();

		if (!seed.getPlayer().equals(currSlotOwner)) {
			_log.debug("Seed owner is different from current slot owner");
			return res;
		}
		
		if (seed.getPlayer().equals(currSlotOwner) && seed.getX() != x) {
			_log.debug("Seed owner equals to current slot owner");
			res++;
		}

		// winning condition (early pruning)
		if (res == Board.WIN_CONDITION - 1) {
			return res;
		}

		return this.checkSouth(seed, x + 1, y, res);
	}
	
	private State checkForwardDiagonal(Seed seed) {
		
		int x = seed.getX();
		int y = seed.getY();
		
		int res = 0;

		res = this.checkNorthEast(seed, x - 1, y + 1, res);
		
		// winning condition
		if (res == Board.WIN_CONDITION - 1) {
			_log.debug("Statisfied winning condition on the north-east side");
			return State.WIN;
		}
		
		res = this.checkSouthWest(seed, x + 1, y - 1, res);
		
		// winning condition
		if (res == Board.WIN_CONDITION - 1) {
			_log.debug("Statisfied winning condition on the south-west side");
			return State.WIN;
		}

		return State.CONTINUE;
	}

	private int checkSouthWest(Seed seed, int x, int y, int res) {
		_log.debug("checkSouthWest - x:" + x + " y:" + y + " res:" + res);

		Player currSlotOwner;

		// reached edge
		if (x == this.size || y == -1) {
			_log.debug("Reached south-west boundary");
			return res;
		}

		// empty slot
		if (this.slots[x][y] == null) {
			_log.debug("Empty slot.");
			return res;
		}

		// check slot owner
		currSlotOwner = this.slots[x][y].getPlayer();

		if (!seed.getPlayer().equals(currSlotOwner)) {
			_log.debug("Seed owner is different from current slot owner");
			return res;
		}
		
		if (seed.getPlayer().equals(currSlotOwner) && seed.getX() != x && seed.getY() != y) {
			_log.debug("Seed owner equals to current slot owner");
			res++;
		}

		// winning condition (early pruning)
		if (res == Board.WIN_CONDITION - 1) {
			return res;
		}

		return this.checkSouthWest(seed, x + 1, y - 1, res);
	}

	private int checkNorthEast(Seed seed, int x, int y, int res) {
		_log.debug("checkNorthEast - x:" + x + " y:" + y + " res:" + res);

		Player currSlotOwner;

		// reached edge
		if (x == -1 || y == this.size) {
			_log.debug("Reached north-east boundary");
			return res;
		}

		// empty slot
		if (this.slots[x][y] == null) {
			_log.debug("Empty slot.");
			return res;
		}

		// check slot owner
		currSlotOwner = this.slots[x][y].getPlayer();

		if (!seed.getPlayer().equals(currSlotOwner)) {
			_log.debug("Seed owner is different from current slot owner");
			return res;
		}
		
		if (seed.getPlayer().equals(currSlotOwner) && seed.getX() != x && seed.getY() != y) {
			_log.debug("Seed owner equals to current slot owner");
			res++;
		}

		// winning condition (early pruning)
		if (res == Board.WIN_CONDITION - 1) {
			return res;
		}

		return this.checkNorthEast(seed, x + 1, y - 1, res);
	}

	private State checkBackDiagonal(Seed seed) {
		int x = seed.getX();
		int y = seed.getY();
		
		int res = 0;

		res = this.checkNorthWest(seed, x - 1, y - 1, res);
		
		// winning condition
		if (res == Board.WIN_CONDITION - 1) {
			_log.debug("Statisfied winning condition on the north-west side");
			return State.WIN;
		}
		
		res = this.checkSouthEast(seed, x + 1, y + 1, res);
		
		// winning condition
		if (res == Board.WIN_CONDITION - 1) {
			_log.debug("Statisfied winning condition on the south-east side");
			return State.WIN;
		}

		return State.CONTINUE;
	}
	
	private int checkNorthWest(Seed seed, int x, int y, int res) {
		_log.debug("checkNorthWest - x:" + x + " y:" + y + " res:" + res);
		
		Player currSlotOwner;
		
		// reached edge
		if (x == -1 || y == -1) {
			_log.debug("Reached north-west boundary");
			return res;
		}
		
		// empty slot
		if (this.slots[x][y] == null) {
			_log.debug("Empty slot.");
			return res;
		}
		
		// check slot owner
		currSlotOwner = this.slots[x][y].getPlayer();
		
		if (!seed.getPlayer().equals(currSlotOwner)) {
			_log.debug("Seed owner is different from current slot owner");
			return res;
		}
		
		if (seed.getPlayer().equals(currSlotOwner) && seed.getX() != x && seed.getY() != y) {
			_log.debug("Seed owner equals to current slot owner");
			res++;
		}
		
		// winning condition (early pruning)
		if (res == Board.WIN_CONDITION - 1) {
			return res;
		}
		
		return this.checkNorthWest(seed, x - 1, y - 1, res);
	}

	private int checkSouthEast(Seed seed, int x, int y, int res) {
		_log.debug("checkSouthEast - x:" + x + " y:" + y + " res:" + res);

		Player currSlotOwner;

		// reached edge
		if (x == this.size || y == this.size) {
			_log.debug("Reached south-east boundary");
			return res;
		}

		// empty slot
		if (this.slots[x][y] == null) {
			_log.debug("Empty slot.");
			return res;
		}

		// check slot owner
		currSlotOwner = this.slots[x][y].getPlayer();

		if (!seed.getPlayer().equals(currSlotOwner)) {
			_log.debug("Seed owner is different from current slot owner");
			return res;
		}
		
		if (seed.getPlayer().equals(currSlotOwner) && seed.getX() != x && seed.getY() != y) {
			_log.debug("Seed owner equals to current slot owner");
			res++;
		}

		// winning condition (early pruning)
		if (res == Board.WIN_CONDITION - 1) {
			return res;
		}

		return this.checkSouthEast(seed, x + 1, y + 1, res);
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((moves == null) ? 0 : moves.intValue());
		result = prime * result
				+ ((playerList == null) ? 0 : playerList.hashCode());
		result = prime * result + size;
		result = prime * result + Arrays.hashCode(slots);
		result = prime * result + ((state == null) ? 0 : state.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Board other = (Board) obj;
		if (moves == null) {
			if (other.moves != null)
				return false;
		} else if (!moves.equals(other.moves))
			return false;
		if (playerList == null) {
			if (other.playerList != null)
				return false;
		} else if (!playerList.equals(other.playerList))
			return false;
		if (size != other.size)
			return false;
		if (!Arrays.equals(slots, other.slots))
			return false;
		if (state != other.state)
			return false;
		return true;
	}
}